package mgo

import (
	"encoding/json"
	"fmt"
	"gitee.com/dreamwood/ez-go/db/mgo/expr"
	"gitee.com/dreamwood/ez-go/ez"
	"go.mongodb.org/mongo-driver/bson"
	"reflect"
	"strings"
	"time"
)

type Filters struct {
	DocConfig *DocConfig
	Dumps     map[string]bool
	Joins     []bson.D
}

func NewFilters() *Filters {
	return &Filters{
		Joins: make([]bson.D, 0),
		Dumps: make(map[string]bool),
	}
}

func CreateFilters(filters map[string]interface{}, docConfig ...*DocConfig) bson.D {
	f := NewFilters()
	if len(docConfig) > 0 {
		f.SetDocConfig(docConfig[0])
	}
	return f.CreateFilter(filters)
}

func (this *Filters) SetDocConfig(docConfig *DocConfig) *Filters {
	this.DocConfig = docConfig
	return this
}

func (this *Filters) filterFormat(filters map[string]interface{}) (newFilters map[string]interface{}) {
	bt, e := json.Marshal(filters)
	if e != nil {
		println(e.Error())
	}
	newFilters = make(map[string]interface{})
	e = json.Unmarshal(bt, &newFilters)
	if e != nil {
		println(e.Error())
	}
	return
}

func (this *Filters) CreateFilter(filters map[string]interface{}) bson.D {
	filters = this.filterFormat(filters)
	ret := make(bson.D, 0)
	for key, filter := range filters {
		operator, keyWithoutOperator := this.getKeyAndOperatorFromString(key)
		//检查并创建Join
		this.PrepareJoins(keyWithoutOperator)
		if filter == nil {
			if operator == "" {
				ret = append(ret, bson.E{Key: keyWithoutOperator, Value: filter})
			} else {
				//其他操作符
				ret = append(ret, bson.E{Key: keyWithoutOperator, Value: bson.M{operator: filter}})
			}
			continue
		}
		//先判断是否是_and或者_or操作
		tp := reflect.TypeOf(filter)
		switch tp.Kind() {
		case reflect.Map:
			v, ok := filter.(map[string]interface{})
			if ok {
				//--以_and开头的为and操作
				if key == "_and" {
					//处理_and开头的字段
					ands := make([]bson.D, 0)
					ands = append(ands, this.CreateFilter(v))
					ret = append(ret, bson.E{Key: expr.AND_C, Value: ands})
				}
				//除了_and以外基本不会有其他情况，这里暂时不做其他处理
			}
		case reflect.Slice, reflect.Array:
			// _or 或者 in
			if key == "_or" {
				//处理_and开头的字段
				orCond := make([]bson.D, 0)
				for _, v := range filter.([]interface{}) {
					orCond = append(orCond, this.CreateFilter(v.(map[string]interface{})))
				}
				ret = append(ret, bson.E{Key: expr.OR_C, Value: orCond})
			} else {
				if operator == "" {
					ret = append(ret, bson.E{Key: keyWithoutOperator, Value: filter})
				} else {
					//其他操作符
					ret = append(ret, bson.E{Key: keyWithoutOperator, Value: bson.M{operator: filter}})
				}
			}
		case reflect.String:
			//字符串类型需要区分出日期
			if strings.HasSuffix(keyWithoutOperator, "At") {
				t, e := time.Parse(time.RFC3339, filter.(string))
				if e != nil {
					ez.LogToConsole(e.Error())
				} else {
					// 时间查询基本上都要用到大于小于,基本上不会碰到直接定制筛选时间的情况
					if operator == "" {
						ret = append(ret, bson.E{Key: keyWithoutOperator, Value: t})
					} else {
						//其他操作符
						ret = append(ret, bson.E{Key: keyWithoutOperator, Value: bson.M{operator: t}})
					}
				}
			} else {
				if operator == "" {
					ret = append(ret, bson.E{Key: keyWithoutOperator, Value: filter})
				} else if operator == "$regex" {
					text, ok := filter.(string)
					if ok {
						text = strings.ReplaceAll(text, `\`, `\\`)
						text = strings.ReplaceAll(text, `"`, `\"`)
						text = strings.ReplaceAll(text, `|`, `\|`)
						text = strings.ReplaceAll(text, `(`, `\(`)
						text = strings.ReplaceAll(text, `)`, `\)`)
						text = strings.ReplaceAll(text, `$`, `\$`)
						//text = strings.ReplaceAll(text, `^`, `\^`)

						ret = append(ret, bson.E{Key: keyWithoutOperator, Value: bson.M{operator: text}})
					}
				} else {
					//其他操作符
					ret = append(ret, bson.E{Key: keyWithoutOperator, Value: bson.M{operator: filter}})
				}
			}
		default:
			if operator == "" {
				ret = append(ret, bson.E{Key: keyWithoutOperator, Value: filter})
			} else {
				//其他操作符
				switch operator {
				case "$regex":
					text, ok := filter.(string)
					if ok {
						text = strings.ReplaceAll(text, `\`, `\\`)
						text = strings.ReplaceAll(text, `"`, `\"`)
						text = strings.ReplaceAll(text, `|`, `\|`)
						text = strings.ReplaceAll(text, `(`, `\(`)
						text = strings.ReplaceAll(text, `)`, `\)`)
						text = strings.ReplaceAll(text, `$`, `\$`)
						text = strings.ReplaceAll(text, `^`, `\^`)

						ret = append(ret, bson.E{Key: keyWithoutOperator, Value: bson.M{operator: text}})
					}
				default:
					ret = append(ret, bson.E{Key: keyWithoutOperator, Value: bson.M{operator: filter}})
				}

			}
		}
	}

	return ret
}

func (this *Filters) getKeyAndOperatorFromString(fullKey string) (operator string, keyWithoutOperator string) {
	//获取key和操作符
	arr := strings.Split(fullKey, "__")
	//只能有两种结果，arr的长度为0或者为1
	if len(arr) == 1 {
		operator = ""
		keyWithoutOperator = arr[0]
	} else {
		operator = fmt.Sprintf("$%s", arr[1])
		keyWithoutOperator = arr[0]
	}
	return
}

func (this *Filters) PrepareJoins(key string) {
	for toFind, _ := range this.Dumps {
		if strings.HasPrefix(key, fmt.Sprintf("%s.", toFind)) {
			delete(this.Dumps, toFind)
		}
	}
	if _, ok := this.Dumps[key]; !ok {
		this.Dumps[key] = true
	}
}

func (this *Filters) CreateJoins() {
	//joins := make([]bson.D, 0)
	dumps := make([]string, 0)
	for keys, _ := range this.Dumps {
		dumps = append(dumps, keys)
	}

	this.Joins = this.createJoinNew(dumps, this.DocConfig)
}

func (this *Filters) createJoin(keyWithoutOperator string, docConfig *DocConfig) (joins []bson.D) {
	if docConfig == nil {
		return
	}
	arr := strings.Split(keyWithoutOperator, ".")
	if len(arr) == 1 {
		//如果是一级关系，那么直接返回
		return
	}
	keyToFind := arr[0]
	for _, relation := range docConfig.RelationFields {
		if relation == keyToFind {
			//检查是否有嵌套的join
			joins = this.createLookup(keyToFind, docConfig, arr[1:]...)
		}
	}
	return
}

func (this *Filters) createLookup(key string, docConfig *DocConfig, deepKeys ...string) []bson.D {

	joinConf := docConfig.RelationConfigs[key]
	if joinConf == nil {
		return nil
	}
	ret := make([]bson.D, 0)

	if joinConf.JoinType == "MM" {
		ret = append(ret, bson.D{{
			expr.UNWIND_C, bson.D{
				{"path", fmt.Sprintf("$%s", joinConf.KeyInside)},
				{"preserveNullAndEmptyArrays", true},
			},
		}})
	}

	params := bson.D{
		{"from", joinConf.DocName},
		{"localField", joinConf.KeyInside},
		{"foreignField", joinConf.KeyOutSide},
		{"as", key},
	}
	//subJoins
	subJoinPipeLine := this.createJoin(strings.Join(deepKeys, "."), joinConf.Config())
	if len(subJoinPipeLine) > 0 {
		params = append(params, bson.E{Key: "pipeline", Value: subJoinPipeLine})
	}
	lookup := bson.D{
		{
			expr.LOOKUP_C,
			params,
		},
	}
	ret = append(ret, lookup)
	if joinConf.JoinType == "O" {
		//ret = append(ret, bson.D{{
		//	expr.UNWIND_C, bson.D{
		//		{"path", fmt.Sprintf("$%s", key)},
		//		{"preserveNullAndEmptyArrays", true},
		//	},
		//}})
	}
	if joinConf.JoinType == "MM" {
		ret = append(ret, bson.D{{
			expr.UNWIND_C, bson.D{
				{"path", fmt.Sprintf("$%s", key)},
				{"preserveNullAndEmptyArrays", true},
			},
		}})
		groupContent := make(bson.D, 0)
		groupContent = append(groupContent, bson.E{Key: "_id", Value: "$id"})
		for _, field := range docConfig.Fields {
			if key == field {
				groupContent = append(groupContent, expr.Push(fmt.Sprintf("%s", field), fmt.Sprintf("$%s", field)))
				groupContent = append(groupContent, expr.Push(fmt.Sprintf("%sIds", field), fmt.Sprintf("$%sIds", field)))
			} else {
				groupContent = append(groupContent, expr.First(field, fmt.Sprintf("$%s", field)))
			}
		}
		ret = append(ret, bson.D{{
			expr.GROUP_C, groupContent,
		}})
	}
	return ret
}

func (this *Filters) createJoinNew(dumps []string, docConfig *DocConfig) (joins []bson.D) {
	if docConfig == nil {
		return
	}
	//把相同前缀的取出来
	toDumps := make(map[string][]string)
	joins = make([]bson.D, 0)
	for _, key := range dumps {
		arr := strings.Split(key, ".")
		if len(arr) == 0 {
			continue
		}
		_, ok := toDumps[arr[0]]
		if !ok {
			toDumps[arr[0]] = make([]string, 0)
		}
		if len(arr) > 1 {
			toAppendDump := strings.Join(arr[1:], ".")
			toDumps[arr[0]] = append(toDumps[arr[0]], toAppendDump)
		}
	}
	for key, subKeys := range toDumps {
		for _, relation := range docConfig.RelationFields {
			if relation == key {
				//检查是否有嵌套的join
				joins = append(joins, this.createLookupNew(key, docConfig, subKeys...)...)
			}
		}
	}
	return
}

func (this *Filters) createLookupNew(key string, docConfig *DocConfig, subKeys ...string) []bson.D {

	joinConf := docConfig.RelationConfigs[key]
	if joinConf == nil {
		return nil
	}
	ret := make([]bson.D, 0)

	if joinConf.JoinType == "MM" {
		ret = append(ret, bson.D{{
			expr.UNWIND_C, bson.D{
				{"path", fmt.Sprintf("$%s", joinConf.KeyInside)},
				{"preserveNullAndEmptyArrays", true},
			},
		}})
		ret = append(ret, bson.D{{
			expr.UNSET_C, []string{fmt.Sprintf("%s", key)},
		}})
	}

	params := bson.D{
		{"from", joinConf.DocName},
		{"localField", joinConf.KeyInside},
		{"foreignField", joinConf.KeyOutSide},
		{"as", key},
	}
	//subJoins
	subJoinPipeLine := make([]bson.D, 0)
	subJoinPipeLine = append(subJoinPipeLine, bson.D{
		{expr.MATCH_C, bson.D{
			{"deleteAt", nil},
		}},
	})
	if len(subKeys) > 0 {
		subJoinPipeLine = append(subJoinPipeLine, this.createJoinNew(subKeys, joinConf.Config())...)
		//if len(subJoinPipeLine) > 0 {
		//
		//}
	}
	params = append(params, bson.E{Key: "pipeline", Value: subJoinPipeLine})
	lookup := bson.D{
		{
			expr.LOOKUP_C,
			params,
		},
	}
	ret = append(ret, lookup)
	//if joinConf.JoinType == "O" {
	ret = append(ret, bson.D{{
		expr.UNWIND_C, bson.D{
			{"path", fmt.Sprintf("$%s", key)},
			{"preserveNullAndEmptyArrays", true},
		},
	}})
	//}
	if joinConf.JoinType == "MM" || joinConf.JoinType == "M" {
		groupContent := make(bson.D, 0)
		groupContent = append(groupContent, bson.E{Key: "_id", Value: "$id"})
		for _, field := range append([]string{"id"}, docConfig.Fields...) {
			if key == field {
				groupContent = append(groupContent, expr.Push(fmt.Sprintf("%s", field), fmt.Sprintf("$%s", field)))
				if joinConf.JoinType == "MM" {
					groupContent = append(groupContent, expr.Push(fmt.Sprintf("%sIds", field), fmt.Sprintf("$%sIds", field)))
				}
			} else {
				//找出关联字段
				find := false
				for f, _ := range docConfig.RelationConfigs {
					if f == field {
						find = true
					}
				}
				if find {
					//如果是关联字段
					switch docConfig.RelationConfigs[field].JoinType {
					case "O":
						groupContent = append(groupContent, expr.First(fmt.Sprintf("%sId", field), fmt.Sprintf("$%sId", field)))
						//groupContent = append(groupContent, expr.First(fmt.Sprintf("%s", field), fmt.Sprintf("$%s", field)))
					case "MM":
						groupContent = append(groupContent, expr.First(fmt.Sprintf("%sIds", field), fmt.Sprintf("$%sIds", field)))
					default:
						//groupContent = append(groupContent, expr.First(fmt.Sprintf("%s", field), fmt.Sprintf("$%s", field)))
					}
					//} else {
					//	groupContent = append(groupContent, expr.First(field, fmt.Sprintf("$%s", field)))
				}
				groupContent = append(groupContent, expr.First(field, fmt.Sprintf("$%s", field)))
			}
		}
		ret = append(ret, bson.D{{
			expr.GROUP_C, groupContent,
		}})
	}
	return ret
}
